/* Try to find the ip of the given mac.
 * 
 * It puts the interface in promiscue mode. It waits for a packet 
 * that it says which ip has the mac given. It stores the result in ip_found.
 * 
 * Functions:
 * int ip_seek(const struct ether_addr *mac, const char *interface, struct in_addr *ip_found )
 * void stop_ipseek()
 * 
 * Changelog:
 * 08/11/06 Guess the ip based in arp packets .
 * 14/11/06 Added the support to udp and ip packet.
 * 
 * Ignacio Lopez
 * nacholopez at gmail dot com
 * 
*/

#include <pcap.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>             /* TODO: I want to rewrite this library, I used too much static variables */
#include <arpa/inet.h>              /* and it needs a timer o cancel function */
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <signal.h>
#include <string.h>


/* dump information to stdout */  
#define debug() 0 /* too much information */
#define debug_2() 0 /* it shows information when the packet is found */

/* filter expression for pcap library */
#define __FILTER_EXPRESSION "arp or ip"

/* Looking for private ips */
#define PRIVATE_IN_CLASSC(a)            ((((in_addr_t)(a)) & 0xFFFF0000) == 0xc0a80000)
#define PRIVATE_IN_CLASSB(a)            ((((in_addr_t)(a)) & 0xFFF00000) == 0xac100000)
#define PRIVATE_IN_CLASSA(a)            ((((in_addr_t)(a)) & 0xFF000000) == 0x0a000000)

/* 
 * Global variables. Todo : The 3 first should be removed.
 */

/* Control when the ip has been found */
int __FLAG_IP_FOUND; // founded

/* Mac address to find */
struct ether_addr *__MAC_TOFIND = NULL; //mac_address

/* Ip founded */
struct in_addr *__IP_FOUND=NULL; //ip_found

sig_atomic_t __stop_ipseekflag = 0;

/* stop the ipseek function */
void stop_ipseek(){
	__stop_ipseekflag = 1;
	if (debug_2()) printf("[ipseek]Flag for stop the ipseek function activated. It should stop\n");
}


/*
 * Return !=0 if the ip is private.
 */
static int __is_private(const struct in_addr *address)
{
	u_int32_t ip;
	ip = ntohl(address->s_addr);

	if (PRIVATE_IN_CLASSC(ip)){
		if (debug()) printf("[ipseek]Ip privada. (C)\n");
		return 1;
	}
	if (PRIVATE_IN_CLASSA(ip)){
		if (debug()) printf("[ipseek]Ip privada. (A)\n");
		return 1;
	}
	if (PRIVATE_IN_CLASSB(ip)){
		if (debug()) printf("[ipseek]Ip privada. (B)\n");
		return 1;
	}
	return 0;
}

/* show in the stdout the packet information */
static void __show_ippacket_information(const struct ether_header *eth_header, const struct iphdr *ip_header)
{
	struct in_addr source_ip, dest_ip;
	memcpy(&source_ip,&ip_header->saddr,sizeof(struct in_addr));
	memcpy(&dest_ip,&ip_header->daddr,sizeof(struct in_addr));
	fprintf(stdout, "[ipseek]IP packet information :\n");
	fprintf(stdout, "[ipseek]source mac: %s", ether_ntoa(eth_header->ether_shost));
	fprintf(stdout, "-dest mac: %s\n", ether_ntoa(eth_header->ether_dhost));
	fprintf(stdout, "[ipseek]source ip: %s", inet_ntoa(source_ip));
	fprintf(stdout, "-dest ip: %s\n", inet_ntoa(dest_ip));	
}

/*
 * dissec an ip packet
 */
static void __handle_ip_packet(const u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
	const struct ether_header *eth_header; 
	const struct iphdr *ip_header;
	struct in_addr source_ip, dest_ip;

	eth_header = (struct ether_header*) packet;
	ip_header = (struct iphdr *) (packet + sizeof(struct ether_header));
	
	memcpy(&source_ip,&ip_header->saddr,sizeof(struct in_addr));
	memcpy(&dest_ip,&ip_header->daddr,sizeof(struct in_addr));

        if(debug()) fprintf(stdout, "[ipseek]IP source[%s]-", inet_ntoa(source_ip));
	if(debug()) fprintf(stdout, "IP destination[%s]\n", inet_ntoa(dest_ip));

	/* If the sender has the mac we are looking for and is a private ip */	
	/* ETH_ALEN == sizeof(struct ether_addr)); */
        if (debug()) fprintf(stdout, "[ipseek] %s Comparing-source ",ether_ntoa(__MAC_TOFIND));
	if (debug()) fprintf(stdout, "%s\n", ether_ntoa(eth_header->ether_shost));	
        if (memcmp(__MAC_TOFIND,eth_header->ether_shost,ETH_ALEN)==0 && __is_private((struct in_addr *)&ip_header->saddr) ){
		if (debug()) printf("[ipseek] %s is equal to %s\n",ether_ntoa(__MAC_TOFIND), ether_ntoa(eth_header->ether_shost));
		memcpy(__IP_FOUND,&source_ip,sizeof(struct in_addr));
		__FLAG_IP_FOUND = 1; /* it will stop the loop in the ipseek funcion */
		if(debug()) printf("[ipseek]Ip: %s[source ip] [%s]\n", inet_ntoa(source_ip), ether_ntoa(eth_header->ether_shost));
		if(debug_2()) __show_ippacket_information(eth_header, ip_header);
		return;
	}
	if (debug()) fprintf(stdout, "[ipseek] %s Comparing-dest ",ether_ntoa(__MAC_TOFIND));
	if (debug()) fprintf(stdout, "%s\n", ether_ntoa(eth_header->ether_dhost));
        if (memcmp(__MAC_TOFIND,eth_header->ether_dhost,ETH_ALEN)==0 && __is_private((struct in_addr *)&ip_header->daddr) ){
		if (debug()) printf("[ipseek] %s is equal to %s\n",ether_ntoa(__MAC_TOFIND), ether_ntoa(eth_header->ether_dhost));
		memcpy(__IP_FOUND,&dest_ip,sizeof(struct in_addr));
		__FLAG_IP_FOUND = 1; /* it will stop the loop in the ipseek funcion */
		if (debug()) printf("[ipseek]Ip: %s[dest ip] [%s]\n", inet_ntoa(dest_ip), ether_ntoa(eth_header->ether_dhost));
		if (debug_2()) __show_ippacket_information(eth_header, ip_header);
		return;
	}
}

/*
 * dissec an arp packet
 */
static void __handle_arp_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{
	const struct ether_header *eth_header; 
	const struct arphdr *arp_header;
	const struct ether_arp *arp_packet;
	struct in_addr ip_src, ip_dst;
	u_int16_t operation;

	eth_header = (struct ether_header*) packet;

	/* MUST TO BE IMPLEMENTED */
	/* Check the ethernet payload == 28 */
	arp_packet = (struct ether_arp*) (packet + sizeof(struct ether_header));
	arp_header = (struct arphdr*) arp_packet;

	/* I'm only interested in arp request and arp replies */
	operation = ntohs(arp_header->ar_op);
	if (operation != ARPOP_REQUEST && operation != ARPOP_REPLY){
		if (debug()) printf("[ipseek]Unknown Arp operation: %d\n", operation);
		return;
	}
	
	bcopy((void *)arp_packet->arp_spa, (void *) &ip_src, sizeof(struct in_addr));
	bcopy((void *)arp_packet->arp_tpa, (void *) &ip_dst, sizeof(struct in_addr));

	if (operation == ARPOP_REQUEST){
		if (debug()) printf("[ipseek]Arp request\n");
	}
	else if (operation == ARPOP_REPLY){
		if (debug()) printf("[ipseek]Arp reply\n");
	}
	/* ETH_ALEN == sizeof(struct ether_addr)); */
	if (memcmp(__MAC_TOFIND, arp_packet->arp_sha, ETH_ALEN)== 0) {
		memcpy(__IP_FOUND, &ip_src, sizeof(struct in_addr));
		__FLAG_IP_FOUND = 1; /* it will stop the loop in the ipseek funcion */
		if (debug_2()) printf("[ipseek]IP found in arp packet: %s:\n",inet_ntoa(ip_src));
		if (debug_2()) {
			fprintf(stdout,"[ipseek]Sender: mac[%s] ",ether_ntoa(arp_packet->arp_sha));
			fprintf(stdout,"ip[%s]\n",inet_ntoa(ip_src));
			fprintf(stdout,"[ipseek]Destination: mac[%s] ",ether_ntoa(eth_header->ether_dhost));
			fprintf(stdout,"ip[%s]\n",inet_ntoa(ip_dst));
		}	
	}	
}

/*
 * dissect/print packet
 */
static void __got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet)
{

	static int __count_packet_number = 1;                   /* packet counter */
	
	/* declare pointers to packet headers */
	const struct ether_header *eth_header;  /* net/ethernet.h */
	u_int16_t eth_type;

	if (debug()) printf("\n[ipseek]Packet number %d:\n", __count_packet_number);
	__count_packet_number++;
	
	/* define ethernet header */
	eth_header = (struct ether_header*)(packet);
	eth_type = ntohs(eth_header->ether_type);

	if (debug()) fprintf(stdout,"[ipseek]ethernet header source: %s ", ether_ntoa(eth_header->ether_shost));
        if (debug()) fprintf(stdout,"destination: %s \n", ether_ntoa(eth_header->ether_dhost));
	
	if (ntohs(eth_header->ether_type) == ETHERTYPE_ARP){
		if (debug()) printf("[ipseek]Packet type: ARP\n");
		__handle_arp_packet(args,header,packet);
		return;
	}
	if (ntohs(eth_header->ether_type) == ETHERTYPE_IP){
		if (debug()) printf("[ipseek]Packet type: IP\n");
		__handle_ip_packet(args,header,packet);
		return;
	}

	if (debug()) printf("[ipseek]Packet unknown\n");
	return;
}

static void __print_error_msg(const char *msg)
{
	fprintf(stderr,msg);
	fprintf(stderr,"\n");
}


/* Try to find the IP from the mac. It stores the result in ip_found. */
int ip_seek(const struct ether_addr *mac, const char *interface, struct in_addr *ip_found )
{
	char errbuf[PCAP_ERRBUF_SIZE];		/* error buffer */
	pcap_t *handle;				/* packet capture handle */
	char filter_exp[] = __FILTER_EXPRESSION;/* filter expression */
	struct bpf_program fp;			/* compiled filter program (expression) */

	/* select stuffs */
	struct timeval tv_select;
	fd_set rfds;
	int pcap_descriptor;

	/* Init the static variables - I don't like too much */
	__FLAG_IP_FOUND = 0;
	/* I belive nobody touch mac value */
	__MAC_TOFIND = (struct ether_addr *) mac; /* the static variable now pointer to the mac we are looking for */
	__IP_FOUND = ip_found; /* the static variable now points to the (struct in_addr *ip_found) */

	/* print capture info */
	if (debug()) printf("[ipseek]Using promiscue mode in interface: %s\n", interface);

	/* open capture device */
	handle = pcap_open_live((char *)interface, BUFSIZ, 1, -1, errbuf); 
	if (handle == NULL) {
		__print_error_msg(errbuf);
		return -1;
	}
	if (debug()) printf("[ipseek]Socket pcap opened\n");

	/* make sure we're capturing on an Ethernet device */
	if (pcap_datalink(handle) != DLT_EN10MB){
		__print_error_msg("[ipseek]The network interface is not ethernet");
		return -1;
	}

	/* compile the filter expression */
	if (pcap_compile(handle, &fp, filter_exp, 0, 0) == -1){
		__print_error_msg(pcap_geterr(handle));
		return -1;
	}

	/* apply the compiled filter */
	if (pcap_setfilter(handle, &fp) == -1){
		__print_error_msg(pcap_geterr(handle));
		return -1;
	}

	pcap_descriptor = pcap_fileno(handle);
	
	int res_select;

	if (debug_2()) printf("[ipseek]Waiting for packets ...\n");
	do {
		/* select stuffs */
		FD_ZERO(&rfds);
		FD_SET(pcap_descriptor, &rfds);
		tv_select.tv_sec = 1; /* max time we wait for a reply */
		tv_select.tv_usec = 500;

		res_select = select (pcap_descriptor + 1, &rfds, NULL, NULL, &tv_select);

		if (FD_ISSET(pcap_descriptor, &rfds)) { /* we have packets waiting */ 
       		/* now we can set our callback function */
			/* it calls __got_packet each time the interface receives a packet */
			if ( pcap_dispatch(handle, 1, __got_packet,  NULL ) < 0){
				__print_error_msg(pcap_geterr(handle));
			}
		} 
	}
	while(__FLAG_IP_FOUND==0 && __stop_ipseekflag==0); 
	
	/* cleanup */
	pcap_freecode(&fp);
	pcap_close(handle);

	if (__stop_ipseekflag != 0){
		/* it restores the flag */
		__stop_ipseekflag = 0;
		if(debug_2()) printf("[ipseek]Ip not found\n");
		return -1;
	}
	return 0;
}

